<?php
/**
 * UriRouter parse the server request URI
 *
 * <p>The uri router parse the URI and returns the matching controller(controller),
 * action and parameters as defined in the routes configuration.
 * </p>
 *
 * <p>Route Definiton can be like as below
 * //static route, just add a .html
 * HTTP_method /sample/{id}      Sample.show
 * //static   redirect
 * //dynamic  redirect
 * //internal redirect
 * //external redirect
 * </p>

 $routes = array(
 // method, url pattern, controller, action, parameters, extension
 array('*','/{controller}/{action}\.(htm|html)','{controller}Controller','{action}'),
 array('*','/static/static','Staticcontroller','index'),
 array('*','/admin/{controller}/{action}\.(htm|html)','Admin{controller}','{action}'),
 array('*','/test/{controller}/{action}/{param2}/{param1}\.(htm|html)','{controller}','{action}','param1, param2'),
 array('*','/test/{controller}/{action}/{param1}-{param2}\.(htm|html)','{controller}','{action}','param1, param2'),
 );
 */
class RCMSRouter {
	protected $defs = array();

	public function __construct($defs) {
		$this->defs = array_merge(array(
			'ROUTE_ID'   => '',
			'REQ_TYPE'   => '',
			'REQ_URL'    => '',
			'REQ_CTRL'   => '',
			'REQ_ACTION' => '',
			'REQ_EXTS' => '',
			'REQ_PARAMS' => '',
		), $defs);
		if (is_null($this->defs['PARSED_URL'])) {
			$this->compile();
		}
	}
	public function getDefinitions() {
		return $this->defs;
	}

	public function compile($routes=null) {
		$urlPattern = '/^';
		$urlMatch = array();
		$url = $this->defs['REQ_URL'];
		$start = 0;
		$end = strlen($url);
		$state = 0;
		while ($start < $end) {
			$pos = $end;
			switch ($state) {
				case 0:  //url text
					if (false !== ($tmpPos = strpos($url, '{', $start))  && $tmpPos < $end) {
						$state = 1;
						$s = str_replace('/', '\/',substr($url, $start, $tmpPos- $start));
						$urlPattern .= str_replace('.', '\.',$s);
						$start = $tmpPos;
					} else {
						$s = str_replace('/', '\/', substr($url, $start));
						$urlPattern .= str_replace('.', '\.',$s);
						$start = $end;
					}
					break;
				case 1:  //variable name
					if (false !== ($tmpPos = strpos($url, '}', $start))  && $tmpPos < $pos) {
						$pos = $tmpPos;
						$state = 0;
					}
					if (false !== ($tmpPos = strpos($url, ':', $start))  && $tmpPos < $pos) {
						$pos = $tmpPos;
						$state = 2;
					}
					$urlMatch[] = substr($url, $start, $pos- $start).'}';
					$start = $pos;
					if ($state===0) {
						$urlPattern.='([^\/]+)';
						$start++;
					}
					if ($pos === $end) {
						throw new RuntimeException('not closed variable name:'.$url);
					}
					break;
				case 2:  // variable regex
					for($i=$start,$deep=1; $i<$end;$i++) {
						$c = substr($url, $i, 1);
						if ($c === '{') $deep++;
						if ($c === '}') $deep--;
						if ($deep===0) break;
					}
					if ($deep!==0) {
						throw new RuntimeException('{ is unbalanced:'.$url);
					}
					$pattern = substr($url, $start+1, $i - $start-1);
					$urlPattern.=$pattern;
					$start = $i+1;
					$state = 0;
					break;
			}
		}
		if (isset($this->defs['REQ_EXTS'])) {
			$ext = '(\.'.str_replace( '/', '|', $this->defs['REQ_EXTS']).')$/';
		} else {
			$ext = '\/?$/';
		}
		$this->defs['PARSED_URL'] = $urlPattern.$ext;
		$this->defs['PARSED_MAP'] = join("/",$urlMatch);
	}

	public function routing($url) {
		if (preg_match($this->defs['PARSED_URL'], $url, $matches)==0) {
			return null;
		}
		$ctrl = $this->defs['REQ_CTRL'];
		$action = $this->defs['REQ_ACTION'];
		$params = (isset($this->defs['REQ_PARAMS']) && strlen($this->defs['REQ_PARAMS'])>0) ? explode('/', $this->defs['REQ_PARAMS']) : array();
		$mapping = array();
		if (isset($this->defs['PARSED_MAP']) && (strlen($this->defs['PARSED_MAP'])>0)) {
			$mapping = explode('/', $this->defs['PARSED_MAP']);
			foreach($mapping as $key=>$value) {
				$mapping[$value] = $matches[$key+1];
				$ctrl = str_replace( $value, ucfirst( $matches[$key+1]), $ctrl);
				$action = str_replace( $value, lcfirst ($matches[$key+1]), $action);
				$params = str_replace( $value,  $matches[$key+1], $params);
			}
		}
		return array('ctrl'=>$ctrl, 'action'=>$action, 'params'=>$params, 'id' => $this->defs['ROUTE_ID']);
	}
}
